package com.easy.mongodb.core.toolkit;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.easy.mongodb.common.annotation.TableField;
import com.easy.mongodb.common.enums.FieldFill;
import com.easy.mongodb.common.enums.FieldType;
import com.easy.mongodb.common.utils.ReflectionKit;
import com.easy.mongodb.common.utils.StringUtils;
import com.easy.mongodb.core.biz.TableInfo;
import com.mongodb.client.model.geojson.Point;
import com.mongodb.client.model.geojson.Position;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.bson.types.ObjectId;

import java.beans.Transient;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.easy.mongodb.common.constants.BaseMongoConstants.DEFAULT_MONGO_ID_NAME;

/**
 * ProductName: easy-mongodb
 * Package: com.easy.mongodb.core.toolkit
 *
 * @Description: Bson工具类
 * @Author: vapeshop
 * @Date: 2022/7/5 13:36
 * UpdateUser: vapeshop
 * UpdateDate: 2022/7/5 13:36
 * UpdateRemark: The modified content
 * @Version: 1.0
 * <p>
 * Copyright © 2022 vapeshop Technologies Inc. All Rights Reserved
 **/
@Slf4j
public class DocumentKit {
    public static <T> List<T> toBeans(List<Document> documents, Class<T> clazz) {
        List<T> list = new ArrayList<T>();
        for (int i = 0; null != documents && i < documents.size(); i++) {
            list.add(toBean(documents.get(i), clazz));
        }
        return list;
    }

    @SuppressWarnings({"rawtypes", "unused", "unchecked"})
    public static <T> T toBean(Document document, Class<T> clazz) {
        if (document == null) {
            return null;
        }
        T entity = null;
        try {
            entity = (T) clazz.newInstance();
            TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
            List<Field> fields = ReflectionKit.getFieldList(clazz);
            for (Field field : fields) {
                field.setAccessible(true);
                //属性类型
                Class fieldClazz = field.getType();
                String key = tableInfo.getMappingColumn(field.getName());
                if (key.equals(tableInfo.getKeyProperty())) {
                    key = DEFAULT_MONGO_ID_NAME;
                }
                Object value = null;
                try {
                    value = field.get(entity);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                Object val = document.get(key);
                if (val == null) {
                    continue;
                }
//                System.out.println("Document key =" + key + ",Document val =" + val + ",toBeanClass =" + fieldClazz + ",toBeanFieldName =" + field.getName());
                if (isPrimitive(fieldClazz) || fieldClazz == String.class) {
                    if (field != null) {
                        if (field.isAnnotationPresent(TableField.class)) {
                            TableField tableField = field.getAnnotation(TableField.class);
                            switch (tableField.fieldType()) {
                                case GEO_POINT:
                                    if (val instanceof Document) {
                                        if ("Point".equals(((Document) val).get("type"))) {
                                            val = ((List<Double>) ((Document) val).get("coordinates")).stream().map(e -> String.valueOf(e)).collect(Collectors.joining(","));
                                        }
                                    }
                                case JOIN:
                                    String aliasField = tableField.condition().field();
                                    if (StringUtils.isNotBlank(aliasField)) {
                                        if (val instanceof List) {
                                            if (((List) val).size() == 0) {
                                                val = "";
                                            } else {
                                                val = ((Document) ((List) val).get(0)).get(aliasField);
                                            }
                                        } else {
                                            System.out.println(val);
                                        }
                                    }
                                default:
                                    break;
                            }

                        }
                        field.set(entity, toPrimitive(val.toString(), field.getType()));
                    }
                    continue;
                }
                //数组
                if (fieldClazz.isArray()) {
                    String itemClazzName = fieldClazz.getTypeName().substring(0, fieldClazz.getTypeName().length() - 2);
                    Class itemClazz = null;
                    try {
                        itemClazz = Class.forName(itemClazzName);
                    } catch (ClassNotFoundException e) {
                        //此时为基本类型
                        itemClazz = toPrimitiveClass(itemClazzName);
                    }
                    Object array = toArray(document.get(key), itemClazz);
                    if (field != null) {
                        field.set(entity, array);
                    }
                    continue;
                }
                //Set
                if (Set.class.isAssignableFrom(fieldClazz)) {
                    ParameterizedType fc = (ParameterizedType) field.getGenericType();
                    TypeVariable[] types = fieldClazz.getTypeParameters();
                    Set list = (Set) value;
                    if (value == null) {
                        list = new HashSet();
                        if (field != null) {
                            toSet(document.get(key), list, (Class) fc.getActualTypeArguments()[0]);
                            field.set(entity, list);
                        }
                    }

                    continue;
                }
                //列表
                if (List.class.isAssignableFrom(fieldClazz)) {
                    ParameterizedType fc = (ParameterizedType) field.getGenericType();
                    TypeVariable[] types = fieldClazz.getTypeParameters();
                    List list = (List) value;
                    if (value == null) {
                        list = new ArrayList<>();
                        if (field != null) {
                            toList(document.get(key), list, (Class) fc.getActualTypeArguments()[0]);
                            field.set(entity, list);
                        }
                    }

                    continue;
                }
                //哈希表
                if (Map.class.isAssignableFrom(fieldClazz)) {
                    ParameterizedType fc = (ParameterizedType) field.getGenericType();
                    Map map = (Map) value;
                    if (value == null) {
                        map = new HashMap<>();
                        if (field != null) {
                            field.set(entity, map);
                        }
                    }
                    toMap(document.get(key), map, (Class) fc.getActualTypeArguments()[0], (Class) fc.getActualTypeArguments()[1]);
                    continue;
                }
                //Date
                if (Date.class.isAssignableFrom(fieldClazz)) {
                    if (Date.class.equals(val.getClass())) {
                        field.set(entity, val);
                    } else {
                        if (field.isAnnotationPresent(TableField.class)) {
                            TableField tableField = field.getAnnotation(TableField.class);
                            field.set(entity, DateUtil.parse(val.toString(), tableField.dateFormat()));
                        } else {
                            field.set(entity, DateUtil.parse(val.toString()));
                        }
                    }
                    continue;
                }
                if (val instanceof List) {
                    if (((List) val).size() == 0) {
                        val = null;
                    } else {
                        val = ((List) val).get(0);
                    }
                }
                //ObjectId
                if (ObjectId.class.isAssignableFrom(fieldClazz)) {
                    if (val instanceof ObjectId) {
                        field.set(entity, val);
                    } else {
                        if (ObjectId.isValid(val.toString())) {
                            field.set(entity, new ObjectId(val.toString()));
                        }

                    }
                    continue;
                }
//                System.out.println(key + "," + val + "," + fieldClazz);
                field.set(entity, toBean((Document) val, fieldClazz));
            }
        } catch (Exception e) {
            log.error("toBean() error , clazz:" + clazz.getName(), e);
        }


        return entity;
    }

    /**
     * 转换成适合update语句的Document对象
     *
     * @param entity
     * @return
     */
    public static Document toDocumentForUpdate(Object entity) {
        Document document = new Document();
        document.put("$set", toDocument(entity, FieldFill.UPDATE));
        if (document.containsKey(DEFAULT_MONGO_ID_NAME)) {
            document.remove(DEFAULT_MONGO_ID_NAME);
        }
        return document;
    }

    /**
     * 转换成Document
     *
     * @param entity
     * @param opType 1
     * @return
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static Document toDocument(Object entity, FieldFill opType) {
        Document document = new Document();
        if (ObjectUtil.isNull(entity)) {
            return document;
        }
        Class clazz = entity.getClass();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
        List<Field> fields = ReflectionKit.getFieldList(clazz);
        for (Field field : fields) {
            field.setAccessible(true);
            Class fieldClazz = field.getType();
            String key = tableInfo.getMappingColumn(field.getName());
            if (key.equals(TableInfoHelper.getTableInfo(clazz).getKeyProperty())) {
                key = DEFAULT_MONGO_ID_NAME;
                if (FieldFill.UPDATE.equals(opType) || FieldFill.INSERT.equals(opType)) {
                    continue;
                }
            }
            Object value = null;
            try {
                value = field.get(entity);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (value == null) {
                if (field.isAnnotationPresent(TableField.class)) {
                    TableField tableField = field.getAnnotation(TableField.class);
                    if (!FieldFill.DEFAULT.equals(opType)) {
                        if (FieldFill.INSERT_UPDATE.equals(tableField.fill())
                                || (FieldFill.INSERT.equals(tableField.fill()) && tableField.fill().equals(opType))
                                || (FieldFill.UPDATE.equals(tableField.fill()) && tableField.fill().equals(opType))) {
                            try {
                                value = fieldClazz.newInstance();
                            } catch (InstantiationException e) {
                                throw new RuntimeException(e);
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        if (value == null) {
                            continue;
                        } else {
                            document.put(key, value);
                            continue;
                        }
                    }
                }
                continue;
            }
            if (fieldClazz.getAnnotationsByType(Transient.class).length > 0) {
                //@Transient 标识的属性不进行转换
                continue;
            }
            if (field.isAnnotationPresent(TableField.class)) {
                TableField tableField = field.getAnnotation(TableField.class);
                if (!tableField.exist() || FieldType.JOIN.equals(tableField.fieldType())) {
                    continue;
                } else if (FieldType.GEO_POINT.equals(tableField.fieldType())) {
                    value = toPrimitive(value.toString(), Point.class);
                } else if (FieldType.OBJECT_ID.equals(tableField.fieldType())) {
                    value = toPrimitive(value.toString(), ObjectId.class);
                } else if (FieldType.DATE.equals(tableField.fieldType())) {
                    if (!field.getType().isAssignableFrom(Date.class)) {
                        LocalDateTime ld = LocalDateTime.parse(value.toString(),
                                DateTimeFormatter.ofPattern(tableField.dateFormat()));
                        value = Date.from(ld.atZone(ZoneId.systemDefault()).toInstant());
                    }
                    document.put(key, value);
                    continue;
                }
            }


            try {
                if (isPrimitive(fieldClazz) || fieldClazz == String.class || fieldClazz.isAssignableFrom(ObjectId.class)) {
                    document.put(key, value);
                    continue;
                }

                if (fieldClazz.isArray()) { //数组
                    String itemClazzName = fieldClazz.getTypeName().substring(0, fieldClazz.getTypeName().length() - 2);
                    Class itemClazz = null;
                    try {
                        itemClazz = Class.forName(itemClazzName);
                    } catch (ClassNotFoundException e) {
                        //此时为基本类型
                        itemClazz = toPrimitiveClass(itemClazzName);
                    }

                    int len = Array.getLength(value);

                    if (isPrimitive(itemClazz) || itemClazz == String.class) {
                        List values = new ArrayList<>();

                        for (int i = 0; i < len; i++) {
                            Object object = Array.get(value, i);
                            values.add(object);
                        }

                        document.put(key, values);
                    } else {
                        List<Document> listDocument = new ArrayList<>();
                        document.put(key, listDocument);

                        for (int i = 0; i < len; i++) {
                            Object object = Array.get(value, i);
                            listDocument.add(toDocument(object, opType));
                        }
                    }
                    continue;
                }
                //Set
                if (Set.class.isAssignableFrom(fieldClazz)) {
                    Set set = (Set) value;
                    ParameterizedType fc = (ParameterizedType) field.getGenericType();
                    Class itemClazz = (Class) fc.getActualTypeArguments()[0];

                    if (isPrimitive(itemClazz) || itemClazz == String.class) {
                        Set values = new HashSet();
                        for (Object object : set) {
                            values.add(object);
                        }
                        document.put(key, values);
                    } else {
                        List<Document> listDocument = new ArrayList<>();
                        document.put(key, listDocument);
                        for (Object object : set) {
                            listDocument.add(toDocument(object, opType));
                        }
                    }
                    continue;
                }
                //列表
                if (List.class.isAssignableFrom(fieldClazz)) {
                    List list = (List) value;
                    ParameterizedType fc = (ParameterizedType) field.getGenericType();
                    Class itemClazz = (Class) fc.getActualTypeArguments()[0];

                    if (isPrimitive(itemClazz) || itemClazz == String.class) {
                        List values = new ArrayList<>();
                        for (Object object : list) {
                            values.add(object);
                        }
                        document.put(key, values);
                    } else {
                        List<Document> listDocument = new ArrayList<>();
                        document.put(key, listDocument);
                        for (Object object : list) {
                            listDocument.add(toDocument(object, opType));
                        }
                    }
                    continue;
                }

                //哈希表
                if (Map.class.isAssignableFrom(fieldClazz)) {
                    Map map = (Map) field.get(entity);
                    Set<Map.Entry> entries = map.entrySet();
                    Map mpperMap = new HashMap<>();
                    document.put(key, mpperMap);

                    ParameterizedType fc = (ParameterizedType) field.getGenericType();
                    Class keyClazz = (Class) fc.getActualTypeArguments()[0];
                    if (keyClazz != String.class && !isPrimitive(keyClazz)) {
                        throw new RuntimeException("不支持的Map,转换成document的key只能为基本类型或字符串");
                    }
                    Class itemClazz = (Class) fc.getActualTypeArguments()[1];
                    if (itemClazz == String.class || isPrimitive(itemClazz)) {
                        for (Map.Entry entry : entries) {
                            mpperMap.put(entry.getKey().toString(), entry.getValue());
                        }
                    } else {
                        for (Map.Entry entry : entries) {
                            mpperMap.put(entry.getKey().toString(), toDocument(entry.getValue(), opType));
                        }
                    }
                    continue;
                }
                document.put(key, toDocument(value, opType));
            } catch (Exception e) {
                log.error("toDocument() , error clazz=" + entity.getClass().getName(), e);
            }
        }
        return document;
    }

    @SuppressWarnings("rawtypes")
    private static boolean isPrimitive(Class clazz) {
        if (clazz.isPrimitive()) {
            return true;
        }
        if (Long.class == clazz || Integer.class == clazz || Double.class == clazz || Float.class == clazz || Short.class == clazz || Boolean.class == clazz || String.class == clazz) {
            return true;
        }
        return false;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private static Object toArray(Object value, Class itemClazz) {
        List list = (List) value;

        Object array = Array.newInstance(itemClazz, list.size());
        int i = 0;
        for (Object object : list) {
            if (object instanceof Document) {
                Array.set(array, i++, toBean((Document) object, itemClazz));
            } else {
                Array.set(array, i++, object);
            }
        }
        return array;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private static void toMap(Object value, Map map, Class keyClazz, Class itemClazz) throws InstantiationException, IllegalAccessException {
        Set<Map.Entry> entries = ((Map) value).entrySet();
        for (Map.Entry entry : entries) {
            Object keyV = entry.getKey().getClass() == String.class ? entry.getKey() : toPrimitive(entry.getKey().toString(), keyClazz);
            Object itemV = entry.getValue();
            if (itemV instanceof Document) {
                map.put(keyV, toBean((Document) itemV, itemClazz));
            } else {
                map.put(keyV, itemV);
            }
        }
    }

    @SuppressWarnings("rawtypes")
    public static Object toPrimitive(Object value, Class clazz) {
        String val = "";
        if (value instanceof Document) {
            val = ((Document) value).getObjectId(DEFAULT_MONGO_ID_NAME).toHexString();
        } else {
            val = value.toString();
        }
        if (int.class == clazz || Integer.class == clazz) {
            return Integer.valueOf(val);
        } else if (long.class == clazz || Long.class == clazz) {
            return Long.valueOf(val);
        } else if (short.class == clazz || Short.class == clazz) {
            return Short.valueOf(val);
        } else if (double.class == clazz || Double.class == clazz) {
            return Double.valueOf(val);
        } else if (float.class == clazz || Float.class == clazz) {
            return Float.valueOf(val);
        } else if (boolean.class == clazz || Boolean.class == clazz) {
            if ("true".equalsIgnoreCase(val) || "false".equalsIgnoreCase(val)) {
                return Boolean.valueOf(val);
            } else if ("1".equalsIgnoreCase(val) || "0".equalsIgnoreCase(val)) {
                return "1".equalsIgnoreCase(val) ? true : false;
            }
            return Boolean.valueOf(val);
        } else if (Point.class == clazz) {
            return new Point(new Position(Arrays.asList(val.split(",")).stream().map(e -> Double.valueOf(e)).collect(Collectors.toList())));
        } else if (String.class == clazz) {
            return val;
        } else if (ObjectId.class == clazz) {
            return new ObjectId(val);
        } else {
            throw new RuntimeException("Map key nonsupport !!!");
        }
    }


    @SuppressWarnings("rawtypes")
    private static Class toPrimitiveClass(String primitiveClazzName) {
        Class itemClazz = null;
        //此时为基本类型
        if ("long".equals(primitiveClazzName)) {
            itemClazz = long.class;
        } else if ("int".equals(primitiveClazzName)) {
            itemClazz = int.class;
        } else if ("short".equals(primitiveClazzName)) {
            itemClazz = short.class;
        } else if ("double".equals(primitiveClazzName)) {
            itemClazz = double.class;
        } else if ("float".equals(primitiveClazzName)) {
            itemClazz = float.class;
        } else if ("boolean".equals(primitiveClazzName)) {
            itemClazz = boolean.class;
        } else {
            throw new RuntimeException("nonsupport type !!!");
        }
        return itemClazz;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private static void toSet(Object value, Set list, Class itemClazz) throws InstantiationException, IllegalAccessException {
        if (value instanceof Collection) {
            Collection<Document> documents = (Collection<Document>) value;
            for (Document document : documents) {
                if (isPrimitive(itemClazz)) {
                    list.add(toPrimitive(document, itemClazz));
                } else {
                    list.add(toBean(document, itemClazz));
                }

            }
        } else {
            List vals = (List) value;
            for (Object object : vals) {
                list.add(toBean((Document) object, itemClazz));
            }
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private static void toList(Object value, List list, Class itemClazz) throws InstantiationException, IllegalAccessException {
        if (value instanceof Collection) {
            Collection<Document> documents = (Collection<Document>) value;
            for (Document document : documents) {
                if (isPrimitive(itemClazz)) {
                    list.add(toPrimitive(document, itemClazz));
                } else {
                    list.add(toBean(document, itemClazz));
                }

            }
        } else {
            List vals = (List) value;
            for (Object object : vals) {
                list.add(toBean((Document) object, itemClazz));
            }
        }
    }
}
